#include <iostream>
#include <fstream>
#include <algorithm>
#include <exception>
#include "CarHire.h"
#include "FunctorsCarHire.h"
#include "ICar.h"
#include "SUV.h"
#include "CompactCar.h"
#include "MidRangeCar.h"
#include "PremiumCar.h"
#include "Xenon.h"
#include "Navigation.h"
#include "CruiseControl.h"
#include "AirConditioner.h"

using namespace std;

//! This function test the CarHire for different output streams (TStream)
template <typename TStream>
bool test_carhire(TStream &stream){
	try{
		//car hire
		CarHire car_rental;
		//lists for testing getter functions of CarHire
		TCars availcars;
		TCars reservedcars;
		TCars availaudis;
		TCars availA6;
		TCars reservedX6;
		TCars availsmartfourfours;
		//cars
		ICar * audiA6 = new PremiumCar("Audi", "A6");
		ICar * bmwX6  = new AirConditioner(new CruiseControl(new SUV("BMW", "X6")));
		ICar * audiQ6 = new Xenon(new AirConditioner(new SUV("Audi","Q6")));
		ICar * smart = new AirConditioner(new Navigation(new CompactCar("Smart", "ForFour")));
		ICar * renaultmegane = new CruiseControl(new AirConditioner(new Xenon(new Navigation(new MidRangeCar("Renault", "Megane")))));
		ICar * vwgolf = new Navigation(new CompactCar("VW", "Golf IV"));
		ICar * audiA8 = new AirConditioner(new PremiumCar("Audi", "A8"));
		//add some cars to the car_rental
		stream << "Add cars to CarHire..." << endl << endl;
		car_rental.Add(audiA6);
		car_rental.Add(bmwX6);
		car_rental.Add(audiQ6);
		car_rental.Add(smart);
		car_rental.Add(renaultmegane);
		//print available and reserved cars
		car_rental.PrintAvailable(stream);
		car_rental.PrintReserved(stream);
		//when a Car is already available nothing happens at moving
		stream << "Move a car to available..." << endl;
		car_rental.MoveToAvailable(audiA6);
		//reserve an existing car
		stream << "Reserve an available car..." << endl;
		car_rental.Reserve(audiQ6);
		//Move a Car to available which is in none of the lists
		stream << "Move a car to available..." << endl << endl;
		car_rental.MoveToAvailable(vwgolf);
		//reserve another car
		car_rental.Add(vwgolf);
		car_rental.Reserve(vwgolf);
		//print available and reserved cars;
		car_rental.PrintAvailable(stream);
		car_rental.PrintReserved(stream);
		//store all available cars in a list
		availcars = car_rental.GetAvailable();
		//print the newly gotten list
		stream << "Print all available cars (list gotten from the Get-Method)..." << endl;
		for_each(availcars.begin(), availcars.end(), PrintCars(stream));
		//store all reserved cars in a list
		reservedcars = car_rental.GetReserved();
		//print the newly gotten list
		stream << "Print all reserved cars (list gotten from the Get-Method)..." << endl;
		for_each(reservedcars.begin(), reservedcars.end(), PrintCars(stream));
		//add another audi to list of available
		car_rental.Add(audiA8);
		//store all available audis in a list
		availaudis = car_rental.GetAvailable("", "Audi");
		//print the newly gotten list
		stream << "Print all available audis (list gotten from the Get-Method)..." << endl;
		for_each(availaudis.begin(), availaudis.end(), PrintCars(stream));
		//store all available type A6 in a list
		availA6 = car_rental.GetAvailable("A6", "");
		//print the newly gotten list
		stream << "Print all available type \"A6\" cars (list gotten from the Get-Method)..." << endl;
		for_each(availA6.begin(), availA6.end(), PrintCars(stream));
		//store all reserved type X6 in a list
		reservedX6 = car_rental.GetAvailable("X6", "");
		//print the newly gotten list
		stream << "Print all available type \"X6\" cars (list gotten from the Get-Method)..." << endl;
		for_each(reservedX6.begin(), reservedX6.end(), PrintCars(stream));
		//store all available smart forfour in a list
		availsmartfourfours = car_rental.GetAvailable("ForFour", "Smart");
		//print the newly gotten list
		stream << "Print all available Smart ForFour's (list gotten from the Get-Method)..." << endl;
		for_each(availsmartfourfours.begin(), availsmartfourfours.end(), PrintCars(stream));
		
		//free all allocated memory
		delete audiA6;
		delete bmwX6;
		delete audiQ6;
		delete smart;
		delete renaultmegane;
		delete vwgolf;
		delete audiA8;
	}
	catch(bad_alloc & ex){
		cerr << ex.what() << endl;
		return false;
	}
	catch(exception & ex){
		cerr << ex.what() << endl;
	}
	return true;
}

int main() {
	//new testfile
	ofstream testfile;
	testfile.open("testfile.txt");
	//test the CarHire for streaming to ostream
	if (!test_carhire(testfile)){
		return 1;
	}
	testfile.close();
	//test the CarHire for streaming to std::cout
	if (!test_carhire(cout)){
		return 1;
	}
	return 0;
}